/*!
 Temelia - Matrix implementation source file.

 Copyright (C) 2008 Ceata (http://ceata.org/proiecte/temelia).

 @author Dascalu Laurentiu

 This program is free software; you can redistribute it and
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 3
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "include/matrix.h"
#include <stdlib.h>

struct _matrix_t
{
	/*! Number of columns */
	int columns;

	/*! Number of rows */
	int rows;

	/*! Elements of the matrix */
	void **data;
};

matrix_t matrix_new(int rows, int columns)
{
	matrix_t mat;
	int i;

	LOGGER("[matrix new] rows %d, columns %d\n", rows, columns);
	_ASSERT(rows , <=, 0, INVALID_INPUT, NULL);
	_ASSERT(columns , <=, 0, INVALID_INPUT, NULL);

	mat = (struct _matrix_t *) _new(sizeof(struct _matrix_t));
	mat->rows = rows;
	mat->columns = columns;

	mat->data = (void **) _new(rows * columns * sizeof(void *));

	if (mat->data == NULL)
	{
		LOGGER("[matrix new] memory allocation failed\n");
		_delete(mat);
		return NULL;
	}

	for (i = 0; i < rows * columns; i++)
		mat->data[i] = NULL;

	LOGGER("[matrix new] succeed and returned %p, data %p\n", mat, mat->data);
	return mat;
}

matrix_t matrix_clone(matrix_t matrix)
{
	matrix_t clone;
	int i, j;

	_ASSERT(matrix, ==, NULL, NULL_POINTER, NULL);

	clone = matrix_new(matrix->rows, matrix->columns);

	_ASSERT(clone, ==, NULL, NULL_POINTER, NULL);

	for (i = 0; i < matrix->rows; i++)
		for (j = 0; j < matrix->columns; j++)
			clone->data[i * matrix->columns + j] = matrix->data[i
					* matrix->columns + j];

	return clone;
}

matrix_t matrix_zeros(int rows, int columns)
{
	matrix_t mat;
	int i, j;

	mat = matrix_new(rows, columns);

	_ASSERT(mat, ==, NULL, NULL_POINTER, NULL);

	for (i = 0; i < rows; i++)
		for (j = 0; j < columns; j++)
			mat->data[i * columns + j] = NULL;

	return mat;
}

void matrix_delete(matrix_t matrix)
{
	_ASSERT(matrix , ==, NULL, NULL_POINTER,);

	_delete(matrix->data);

	matrix->columns = matrix->rows = 0;

	_delete(matrix);
}

void matrix_resize(matrix_t matrix, int rows, int columns)
{
	void **data, *aux;
	int i, j;

	LOGGER("[matrix resize] matrix = %p, new_rows = %d, new_columns = %d\n",
			matrix, rows, columns);
	_ASSERT(matrix, ==, NULL, NULL_POINTER,);

	// I don't resize a matrix to a little dimension
	if (matrix->rows > rows || matrix->columns > columns)
		return;

	data = (void **) _new(rows * columns * sizeof(void *));

	_ASSERT(data, ==, NULL, NULL_POINTER,);

	LOGGER("[matrix resize] clearing new allocated matrix\n");
	for (i = 0; i < rows * columns; i++)
		data[i] = NULL;

	LOGGER("[matrix resize] copying to old matrix into new matrix\n");
	for (i = 0; i < matrix->rows; i++)
		for (j = 0; j < matrix->columns; j++)
			data[i * columns + j] = matrix->data[i * matrix->columns + j];

	LOGGER("[matrix resize] clearing the old resized matrix\n");
	aux = matrix->data;
	matrix->data = data;
	_delete(aux);
	matrix->columns = columns;
	matrix->rows = rows;
}

void matrix_set_key_at(matrix_t matrix, int row, int column, void *key)
{
	LOGGER("[matrix set key at] matrix %p, row %d, column %d, key %p\n",
			matrix, row, column, key);

	_ASSERT(matrix, ==, NULL, NULL_POINTER,);
	_ASSERT(row, <, 0, INVALID_INPUT,);
	_ASSERT(row, >=, matrix->rows, INVALID_INPUT,);
	_ASSERT(column, <, 0, INVALID_INPUT,);
	_ASSERT(column, >=, matrix->columns, INVALID_INPUT,);

	matrix->data[row * matrix->columns + column] = key;
}

void *matrix_get_key_at(matrix_t matrix, int row, int column)
{
	LOGGER("[matrix get key at] matrix %p, row %d, column %d\n", matrix, row,
			column);

	_ASSERT(matrix, ==, NULL, NULL_POINTER, NULL);
	_ASSERT(row, <, 0, INVALID_INPUT, NULL);
	_ASSERT(row, >=, matrix->rows, INVALID_INPUT, NULL);
	_ASSERT(column, <, 0, INVALID_INPUT, NULL);
	_ASSERT(column, >=, matrix->columns, INVALID_INPUT, NULL);

	return matrix->data[row * matrix->columns + column];
}

int matrix_get_rows_number(matrix_t matrix)
{
	_ASSERT(matrix, ==, NULL, NULL_POINTER, -1);

	return matrix->rows;
}

int matrix_get_columns_number(matrix_t matrix)
{
	_ASSERT(matrix, ==, NULL, NULL_POINTER, -1);

	return matrix->columns;
}

void *matrix_get_implementation(matrix_t matrix)
{
	_ASSERT(matrix, ==, NULL, NULL_POINTER, NULL);

	return matrix->data;
}

void **matrix_get_row(matrix_t matrix, int row)
{
	void **data;
	int i;

	_ASSERT(matrix, ==, NULL, NULL_POINTER, NULL);
	_ASSERT(row, <, 0, INVALID_INPUT, NULL);
	_ASSERT(row, >=, matrix->rows, INVALID_INPUT, NULL);

	data = (void **) _new(matrix->columns * sizeof(void *));

	_ASSERT(data, ==, NULL, NULL_POINTER, NULL);

	for (i = 0; i < matrix->columns; i++)
		data[i] = matrix->data[row * matrix->columns + i];

	return data;
}

void **matrix_get_column(matrix_t matrix, int column)
{
	void **data;
	int i;

	_ASSERT(matrix, ==, NULL, NULL_POINTER, NULL);
	_ASSERT(column, <, 0, INVALID_INPUT, NULL);
	_ASSERT(column, >=, matrix->columns, INVALID_INPUT, NULL);

	data = (void **) _new(matrix->rows * sizeof(void *));

	_ASSERT(data, ==, NULL, NULL_POINTER, NULL);

	for (i = 0; i < matrix->rows; i++)
		data[i] = matrix->data[i * matrix->columns + column];

	return data;
}

void matrix_swap_rows(matrix_t matrix, int first_row, int second_row)
{
	void *aux;
	int i;

	_ASSERT(matrix, ==, NULL, NULL_POINTER,);
	_ASSERT(first_row, <, 0, INVALID_INPUT,);
	_ASSERT(first_row, >=, matrix->rows, INVALID_INPUT,);
	_ASSERT(second_row, <, 0, INVALID_INPUT,);
	_ASSERT(second_row, >=, matrix->rows, INVALID_INPUT,);

	for (i = 0; i < matrix->columns; i++)
	{
		aux = matrix->data[first_row * matrix->columns + i];
		matrix->data[first_row * matrix->columns + i] = matrix->data[second_row
				* matrix->columns + i];
		matrix->data[second_row * matrix->columns + i] = aux;
	}
}

void matrix_swap_columns(matrix_t matrix, int first_column, int second_column)
{
	void *aux;
	int i;

	_ASSERT(matrix, ==, NULL, NULL_POINTER,);
	_ASSERT(first_column, <, 0, INVALID_INPUT,);
	_ASSERT(first_column, >=, matrix->columns, INVALID_INPUT,);
	_ASSERT(second_column, <, 0, INVALID_INPUT,);
	_ASSERT(second_column, >=, matrix->columns, INVALID_INPUT,);

	for (i = 0; i < matrix->rows; i++)
	{
		aux = matrix->data[i * matrix->columns + first_column];
		matrix->data[i * matrix->columns + first_column] = matrix->data[i
				* matrix->columns + second_column];
		matrix->data[i * matrix->columns + second_column] = aux;
	}
}

matrix_t matrix_transpose(matrix_t matrix)
{
	matrix_t transposed;
	int i, j;

	_ASSERT(matrix, ==, NULL, NULL_POINTER, NULL);

	transposed = matrix_new(matrix->columns, matrix->rows);

	for (i = 0; i < matrix->rows; i++)
		for (j = 0; j < matrix->columns; j++)
			transposed->data[j * matrix->columns + i] = matrix->data[i * matrix->columns
					+ j];

	return transposed;
}

void matrix_iterate(matrix_t matrix,
		void key_handler(void *key, void *context), void *context)
{
	int i, j;

	_ASSERT(matrix, ==, NULL, NULL_POINTER,);
	_ASSERT(key_handler, ==, NULL, NULL_POINTER,);

	for (i = 0; i < matrix->rows; i++)
		for (j = 0; j < matrix->columns; j++)
			key_handler(matrix->data[i * matrix->columns + j], context);
}
